﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Box2D.XNA;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;
using System.Threading;
using System.Reflection;
using Microsoft.Xna.Framework.Graphics;

namespace Jemgine.Engine
{
    class EntityListSearcher : TweakableCollectionSearch
    {
        public object Search(object Collection, string str)
        {
            foreach (var Entity in (Collection as List<Data.EntityRecord>))
                if (String.Compare(Entity.Name, str, false) == 0) return Entity;
            return null;
        }
    }

    public partial class Simulation
    {
        public Settings settings;

        [TweakableCollection("Entities", typeof(EntityListSearcher))]
        public List<Data.EntityRecord> Entities;

        [Tweakable("debug_physics")]
        public bool DebugPhysics = false;

        [Tweakable("debug_nav")]
        public bool DebugNavigation = false;

        [Tweakable("display_shadowmap")]
        public bool DisplayShadowmap = false;

        public Data.MapData RawMap = null;
        public Data.MapHeader MapHeader = null;

        public bool SupressPhysics = false;

        public ContentManager EngineContent { get; private set; }
        public EpisodeContentManager EpisodeContent { get; private set; }
        public Episode LoadedEpisode { get; private set; }

        BufferedList<Data.ScriptNode> _created;
        BufferedList<Data.EntityRecord> _destroyed;
        BufferedList<Data.Dynamic> _createdJoints;

        private List<Module> Modules = new List<Module>();
        public CameraModule CameraModule = new CameraModule();

        public Renderer.VertexPusher VertexPusher { get; private set; }
        public Renderer.RenderEffect Effect { get; private set; }
        public Renderer.Driver Driver { get; private set; }

        public int TickCount = 0;

        public void AddModule(Module _m)
        {
            Modules.Add(_m);
            _m.Sim = this;
        }

        public T GetModule<T>() where T : class
        {
            foreach (Module m in Modules)
                if (m is T) return m as T;
            return null;
        }

        public Simulation(IServiceProvider Services, GraphicsDevice Device, Settings settings)
        {
            this.settings = settings;
            this.EngineContent = new ContentManager(Services);
            Entities = new List<Data.EntityRecord>();

            EpisodeContent = new EpisodeContentManager(Services, settings.ContentDirectory, settings.Episode);
            LoadedEpisode = EpisodeContent.Module;

            AddModule(new PhysicsModule(settings));
            AddModule(new ScriptModule());
            AddModule(new AnimationModule());
            AddModule(new ActionIconModule());
            AddModule(new Particles.ParticleModule(settings, Device, EngineContent, EpisodeContent));
            AddModule(CameraModule);
        }

        public void InitializeRenderer(GraphicsDevice Device)
        {
            VertexPusher = new Renderer.VertexPusher(Device);
            Effect = new Renderer.RenderEffect(Device, EngineContent);
            Driver = new Renderer.Driver(Device, EpisodeContent);
        }

        public void Draw(GraphicsDevice Device)
        {
            if (Effect == null) InitializeRenderer(Device);
            Driver.DrawMap(Entities, MapHeader, Device, CameraModule.Camera, Effect, VertexPusher, EpisodeContent);
            foreach (var Module in Modules) Module.Draw(Effect, VertexPusher);
        }

        public void Draw(GraphicsDevice Device, Jemgine.Data.MapData Map, Vector2 Offset)
        {
            if (Effect == null) InitializeRenderer(Device);
            Effect.PushMatrix(Matrix.CreateTranslation(new Vector3(Offset, 0.0f)));
            Driver.DrawSimpleMap(Map, Device, CameraModule.Camera, Effect, VertexPusher, EpisodeContent);
            Effect.PopMatrix();
        }

        public void Update(float ElapsedSeconds)
        {
            TickCount += 1;

            PhysicsModule physicsModule = GetModule<PhysicsModule>();

            _created.Swap();

            for (int i = 0; i < _created.Count; ++i)
            {
                if (_created[i] is Data.EntityRecord)
                {
                    var Ent = _created[i] as Data.EntityRecord;
                    if (Ent.state != Jemgine.Data.EntityRecord.State.PreCreated)
                    {
                        _created[i].Initialize(EpisodeContent);
                        Ent.state = Jemgine.Data.EntityRecord.State.Initialized;
                    }
                    Entities.Add(Ent);
                }
                else
                    _created[i].Initialize(EpisodeContent);
            }

            _created.ClearFront();

            _createdJoints.Swap();
            for (int i = 0; i < _createdJoints.Count; ++i)
                physicsModule.CreateJoint(_createdJoints[i]);
            _createdJoints.ClearFront();

            _destroyed.Swap();

            for (int i = 0; i < _destroyed.Count; ++i)
            {
                Data.EntityRecord E = _destroyed[i];
                if (E != null)
                {
                    if (E.state != Data.EntityRecord.State.PreCreated)
                    {
                        E.Destroy();
                        E.state = Jemgine.Data.EntityRecord.State.Dead;
                    }
                    Entities.Remove(E);
                }
            }

            _destroyed.ClearFront();

            foreach (Module _module in Modules)
                _module.PreUpdate();
            foreach (Module _module in Modules)
                _module.Update(ElapsedSeconds);
        }

        public void DestroyEntity(Data.EntityRecord E)
        {
            System.Diagnostics.Debug.Assert(E.state != Jemgine.Data.EntityRecord.State.PreCreated);
            _destroyed.Add(E);
        }

        public void AddPrecreatedEntity(Data.EntityRecord E)
        {
            System.Diagnostics.Debug.Assert(E.state == Jemgine.Data.EntityRecord.State.PreCreated);
            E.SetActive(true);
            _created.Add(E);
        }

        public void RemovePrecreatedEntity(Data.EntityRecord E)
        {
            System.Diagnostics.Debug.Assert(E.state == Jemgine.Data.EntityRecord.State.PreCreated);
            E.SetActive(false);
            _destroyed.Add(E);
        }

        public void InitializePrecreatedEntity(Data.EntityRecord E, bool PostProcess)
        {
            if (PostProcess) E.PostProcess(Vector2.Zero, RawMap, this);
            E.Initialize(EpisodeContent);
            E.state = Jemgine.Data.EntityRecord.State.PreCreated;
        }

        public void DestroyPrecreatedEntity(Data.EntityRecord E)
        {
            System.Diagnostics.Debug.Assert(E.state == Jemgine.Data.EntityRecord.State.PreCreated);
            E.Destroy();
        }

        public static Box2D.XNA.AABB GetBoundingBox(Box2D.XNA.Body Body)
        {
            var BoundingBox = new Box2D.XNA.AABB();
            var Fixtures = Body.GetFixtureList();
            var Transform = new Box2D.XNA.Transform();
            Body.GetTransform(out Transform);
            if (Fixtures != null)
            {
                BoundingBox = GetBoundingBox(Fixtures, ref Transform);
                Fixtures = Fixtures.GetNext();
                while (Fixtures != null)
                {
                    var BBox = new Box2D.XNA.AABB();
                    GetBoundingBox(Fixtures, ref Transform);
                    BoundingBox.Combine(ref BBox);
                }
            }
            return BoundingBox;
        }

        public static Box2D.XNA.AABB GetBoundingBox(Box2D.XNA.Fixture Fixture, ref Box2D.XNA.Transform Transform)
        {
            var BoundingBox = new Box2D.XNA.AABB();
            var Shape = Fixture.GetShape();
            Shape.ComputeAABB(out BoundingBox, ref Transform, 0);
            for (int i = 1; i < Shape.GetChildCount(); ++i)
            {
                var BBox = new Box2D.XNA.AABB();
                Shape.ComputeAABB(out BBox, ref Transform, i);
                BoundingBox.Combine(ref BBox);
            }
            return BoundingBox;
        }
    }
}
